#include "pch.h"
#include "DirVisitor.h"
#include "GaojieDriveApp.h"
#include "AppConfig.h"
#include "CloudManager.h"
#include "..\Common\SRWLock.h"
#include "..\Common\StrHelper.h"
#include "..\Common\Utility.h"

CDirVisitor::CDirVisitor(const CF_CALLBACK_INFO *pCallbackInfo, LockExit lockExit, std::wstring strPathName)
	: m_bCanceled(false)
	, m_bError(false)
	, m_cfCbWrapper(pCallbackInfo)
	, m_lockExit(std::move(lockExit))
	, m_strPathName(std::move(strPathName))
{
}

CDirVisitor::~CDirVisitor()
{
	assert(m_lockExit);

	GetApp()->GetCloudManager()->RemoveTask(GetKey());

	if (m_bCanceled)
		return;

	TransferPlaceholders();
}

INT64 CDirVisitor::GetKey() const
{
	return m_cfCbWrapper.GetTransferKey();
}

void CDirVisitor::Cancel()
{
	m_bCanceled = true;
}

void CDirVisitor::OnVisit(FileInfo fileInfo)
{
	AssertInOrderedTask();

	if (m_bCanceled)
		return;

	std::wstring strFileName = Str::StringToLower(fileInfo.strFileName);

	// Update if it exists locally, otherwise save it
	CExclusiveLock lock(m_SRWLock);
	auto it = m_mapLocalInfos.find(strFileName);
	if (it != m_mapLocalInfos.end() && CanUpdate(it->second, fileInfo))
	{
		auto localInfo = std::move(it->second);
		m_mapLocalInfos.erase(it);
		lock.Unlock();
		UpdatePlaceholder(std::move(localInfo), std::move(fileInfo));
	}
	else
		m_mapRemoteInfos.emplace(std::move(strFileName), std::move(fileInfo));
}

void CDirVisitor::OnError()
{
	AssertInOrderedTask();

	if (m_bCanceled)
		return;

	// Handle in the destructor when the reference count is 0
	m_bError = true;
}

void CDirVisitor::Init()
{
	// Enumerate local files while enumerating remote files
	AT::AsyncTask(&CDirVisitor::EnumLocalFiles, shared_from_this());
}

// Enumerate locally existing files
void CDirVisitor::EnumLocalFiles()
{
	AssertInThreadpool();

	Util::EnumDirectory((m_strPathName + L'*').c_str(), [this](FIND_DATA&& findData)
	{
		std::wstring strFileName = Str::StringToLower(findData.cFileName);

		// Update if it exists remotely, otherwise save it
		CExclusiveLock lock(m_SRWLock);
		auto it = m_mapRemoteInfos.find(strFileName);
		if (it != m_mapRemoteInfos.end() && CanUpdate(findData, it->second))
		{
			auto remoteInfo = std::move(it->second);
			m_mapRemoteInfos.erase(it);
			lock.Unlock();
			AT::AsyncTask(&CDirVisitor::UpdatePlaceholder, shared_from_this(), std::move(findData), std::move(remoteInfo));
		}
		else
			m_mapLocalInfos.emplace(std::move(strFileName), std::move(findData));

		return true;
	});
}

bool CDirVisitor::CanUpdate(const FIND_DATA& localInfo, const FileInfo& remoteInfo) const
{
	if ((localInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != (remoteInfo.dwAttributes & FILE_ATTRIBUTE_DIRECTORY))
		return false;

	if ((localInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 && (localInfo.nFileSizeLow || localInfo.nFileSizeHigh) && remoteInfo.nFileSize == 0)
		return false;

	return true;
}

void CDirVisitor::UpdatePlaceholder(FIND_DATA&& localInfo, FileInfo&& remoteInfo) const
{
	AssertInThreadpool();

	CF::UpdatePlaceholder(m_strPathName, localInfo, remoteInfo);
}

void CDirVisitor::TransferPlaceholders()
{
	AssertInThreadpool();

	std::vector<FileInfo> vecFileInfos;

	if (!m_bError)
	{
		// Delete extra files first
		if (m_mapLocalInfos.size())
			DeleteExtraFiles();

		// Create placeholders that don't exist locally
		vecFileInfos.reserve(m_mapRemoteInfos.size());

		for (auto& [key, fileInfo] : m_mapRemoteInfos)
		{
			vecFileInfos.emplace_back(std::move(fileInfo));
		}
	}

	m_cfCbWrapper.TransferPlaceholders(m_strPathName, m_bError, vecFileInfos);
}

// Delete local files that do not exist on the remote
void CDirVisitor::DeleteExtraFiles() const
{
	AssertInThreadpool();

	std::wstring strFilePath = m_strPathName;
	std::wstring strTempPath = CF::GetRecycleTempPath();
	const size_t nPathLen1 = strFilePath.size();
	const size_t nPathLen2 = strTempPath.size();

	for (auto& [key, findData] : m_mapLocalInfos)
	{
		strFilePath += findData.cFileName;
		strTempPath += findData.cFileName;

		if (!MoveFileExW(strFilePath.c_str(), strTempPath.c_str(), 0))
			LogWarn(L"MoveFileEx error: %s, Error=%d", strFilePath, GetLastError());

		strFilePath.resize(nPathLen1);
		strTempPath.resize(nPathLen2);
	}

	enum { TID_CleanRecycle };
	AT::OrderedTask(TID_CleanRecycle, [strPathName = std::move(strTempPath)]
	{
		Util::DeleteDirectory(strPathName.c_str());
	});
}
